package cn.uncode.springcloud.gateway.route;


import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import cn.uncode.springcloud.gateway.canary.CanaryStrategy;
import cn.uncode.springcloud.gateway.canary.DefaultCanaryStrategy;
import cn.uncode.springcloud.gateway.ribbon.RibbonContextHolder;
import cn.uncode.springcloud.utils.json.JsonUtil;
import cn.uncode.springcloud.utils.net.RestTemplateUtil;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {
	
	private static final String ADMIN_SERVER_NAME = "uncode-admin";
	
	private static volatile long CURRENT_GATEWAY_ROUTE_VERSION = 0;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    @Autowired
    private ApplicationEventPublisher publisher;
    
    @Autowired
    private EurekaClient eurekaClient;
    
    @Autowired
    private DefaultCanaryStrategy defaultCanaryStrategy;


    /**
     * 增加路由
     * @param definition
     * @return
     */
    public void add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        log.info("添加路由规则");
    }


    /**
     * 更新路由
     * @param definition
     * @return
     */
    public void update(RouteDefinition definition) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            log.error("update fail,not find route  routeId: "+definition.getId());
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
        	log.error("update route  fail");
        }
        log.info("修改路由规则");
    }
    /**
     * 删除路由
     * @param id
     * @return
     */
    public void delete(String id) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(id));
        } catch (Exception e) {
            log.error("删除路由规则失败", e);
        }
        log.info("删除路由规则");

    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    
    public String refresh(boolean force){
        try{
        	List<String> instances = getServiceInstance(ADMIN_SERVER_NAME);
        	if(instances != null && instances.size() > 0) {
        		String url = "http://"+ instances.get(0) +"/gateway/routes/0";
        		if(!force) {
        			url = "http://"+ instances.get(0) +"/gateway/routes/" + CURRENT_GATEWAY_ROUTE_VERSION;
        		}
            	ResponseEntity<Map> responseEntity = RestTemplateUtil.get(url, Map.class);
            	if(responseEntity != null && responseEntity.getBody() != null) {
            		String codeStr = String.valueOf(responseEntity.getBody().get("code"));
            		String message = String.valueOf(responseEntity.getBody().get("message"));
            		if(StringUtils.isNotBlank(codeStr)) {
            			int code = Integer.parseInt(codeStr);
            			CURRENT_GATEWAY_ROUTE_VERSION = Long.parseLong(message);
            			if(code == 204) {
            				
            			}else if(code == 200) {
            				Object rt = responseEntity.getBody().get("data");
            				if(rt != null) {
            					List<Map> list = (List<Map>)rt;
            					for(Map item : list){
            						RouteDefinition routeDefinition = new RouteDefinition();
            						routeDefinition.setId(String.valueOf(item.get("routeId")));
            						if(item.containsKey("routeOrder")) {
            							String order = String.valueOf(item.get("routeOrder"));
            							int ord = 0;
            							if(StringUtils.isNotBlank(order)) {
            								ord = Integer.parseInt(order);
            							}
            							routeDefinition.setOrder(ord);
            						}
            						String uriStr = String.valueOf(item.get("routeUri"));
            						URI uri = null;
            						try {
            							uri = UriComponentsBuilder.fromHttpUrl(uriStr).build().toUri();
									} catch (Exception e) {}
            						routeDefinition.setUri(uri);
            						if(item.containsKey("predicates")) {
            							String predicates = String.valueOf(item.get("predicates"));
            							if(StringUtils.isNotBlank(predicates)) {
            								List<PredicateDefinition> plist = JsonUtil.parseArray(predicates, PredicateDefinition.class);
            								routeDefinition.setPredicates(plist);
            							}
            						}
            						if(item.containsKey("filters")) {
            							String filters = String.valueOf(item.get("filters"));
            							if(StringUtils.isNotBlank(filters)) {
            								List<FilterDefinition> flist = JsonUtil.parseArray(filters, FilterDefinition.class);
            								routeDefinition.setFilters(flist);
            							}
            						}
            						String status = String.valueOf(item.get("status"));
        							int stat = 1;
        							if(StringUtils.isNotBlank(status)) {
        								stat = Integer.parseInt(status);
        							}
        							switch (stat) {
    								case 1:
    									add(routeDefinition);
    									break;
    								case 2:
    									update(routeDefinition);
    									break;
    								case 3:
    									delete(routeDefinition.getId());
    									break;
    								default:
    									break;
    								}
            					}
            					return String.valueOf(rt);
            				}
            			}
            		}
            	}
        	}
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
    }
    
    
    public String canaryRefresh(){
    	reloadCanaryServiceInstance();
        try{
        	List<String> instances = getServiceInstance(ADMIN_SERVER_NAME);
        	if(instances != null && instances.size() > 0) {
        		String url = "http://"+ instances.get(0) +"/canary/info";
            	ResponseEntity<Map> responseEntity = RestTemplateUtil.get(url, Map.class);
            	if(responseEntity != null && responseEntity.getBody() != null) {
            		String codeStr = String.valueOf(responseEntity.getBody().get("code"));
            		if(StringUtils.isNotBlank(codeStr)) {
            			int code = Integer.parseInt(codeStr);
            			if(code == 204) {
            				
            			}else if(code == 200) {
            				Object rt = responseEntity.getBody().get("data");
            				if(rt != null) {
            					List<Map> list = (List<Map>)rt;
            					for(Map item : list){
            						String appName = String.valueOf(item.get("name"));
            						String flag = String.valueOf(item.get("flag"));
            						String type = String.valueOf(item.get("type"));
            						if(StringUtils.isNotBlank(appName) && StringUtils.isNotBlank(flag) && StringUtils.isNotBlank(type)) {
            							String key = String.valueOf(item.get("key"));
                						String value = String.valueOf(item.get("value"));
                						if(CanaryStrategy.DEFAULT_STRATEGY_TYPE_IP.equals(type)) {
                							if(StringUtils.isNotBlank(value)) {
                								String[] vals = value.split(",");
                								for(String val:vals) {
                									if(StringUtils.isNotBlank(val)){
                										defaultCanaryStrategy.getIp2flag().put(val, flag);
                									}
                								}
                							}
                						}else if(CanaryStrategy.DEFAULT_STRATEGY_TYPE_REQUEST.equals(type)) {
                							if(StringUtils.isNotBlank(key)) {
                								defaultCanaryStrategy.getKeyInRequest2flag().put(key, flag);
                								Set<String> sets = defaultCanaryStrategy.getKeyInRequest2value().get(key);
                								if(sets == null) {
                									sets = new HashSet<>();
                									defaultCanaryStrategy.getKeyInRequest2value().put(key, sets);
                								}
                								if(StringUtils.isNotBlank(value)) {
                    								String[] vals = value.split(",");
                    								for(String val:vals) {
                    									if(StringUtils.isNotBlank(val)){
                    										sets.add(val);
                    									}
                    								}
                    							}
                							}
                						}else if(CanaryStrategy.DEFAULT_STRATEGY_TYPE_SESSION.equals(type)) {
                							if(StringUtils.isNotBlank(key)) {
                								defaultCanaryStrategy.getKeyInSession2flag().put(key, flag);
                								Set<String> sets = defaultCanaryStrategy.getKeyInSession2value().get(key);
                								if(sets == null) {
                									sets = new HashSet<>();
                									defaultCanaryStrategy.getKeyInSession2value().put(key, sets);
                								}
                								if(StringUtils.isNotBlank(value)) {
                    								String[] vals = value.split(",");
                    								for(String val:vals) {
                    									if(StringUtils.isNotBlank(val)){
                    										sets.add(val);
                    									}
                    								}
                    							}
                							}
                						}
            						}
            					}
            				}
            				return String.valueOf(rt);
            			}
            		}
            	}
        	}
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
    }
    
    /**
     * 加载所有灰度节点信息
     */
    private void reloadCanaryServiceInstance(){
		if(null != eurekaClient) {
			List<Application> apps = eurekaClient.getApplications().getRegisteredApplications();
			for(Application app : apps){
				List<InstanceInfo> instances = app.getInstancesAsIsFromEureka();
				for(InstanceInfo instance : instances){
					if(instance.getMetadata().containsKey("canaryFlag")) {
						String flag = instance.getMetadata().get("canaryFlag");
						if(StringUtils.isNotBlank(flag)) {
							RibbonContextHolder.addFlag2AppName(flag, app.getName());
						}
					}
				}
			}
		}
	}
    
    
    private List<String> getServiceInstance(String appName){
		List<String> servers = new ArrayList<>();
		if(null != eurekaClient) {
			List<Application> apps = eurekaClient.getApplications().getRegisteredApplications();
			for(Application app : apps){
				if(app.getName().toLowerCase().equals(appName.toLowerCase())) {
					List<InstanceInfo> instances = app.getInstancesAsIsFromEureka();
					for(InstanceInfo instance : instances){
						servers.add(instance.getIPAddr()+":"+instance.getPort());
					}
				}
			}
		}
		return servers;
	}
    
    

    /**
     * spring:
     cloud:
     gateway:
     routes: #当访问http://localhost:8080/baidu,直接转发到https://www.baidu.com/
     - id: baidu_route
     uri: http://baidu.com:80/
     predicates:
     - Path=/baidu
     * @param args
     */

    public static void main(String[] args) {
        RouteDefinition definition = new RouteDefinition();
        PredicateDefinition predicate = new PredicateDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        definition.setId("jd_route");
        predicate.setName("Path");
        predicateParams.put("pattern", "/jd");
        predicate.setArgs(predicateParams);
        definition.setPredicates(Arrays.asList(predicate));
//        String uri="http://www.jd.com";
        URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
        definition.setUri(uri);
//        System.out.println("definition:"+JSON.toJSONString(definition));


        RouteDefinition definition1 = new RouteDefinition();
        PredicateDefinition predicate1 = new PredicateDefinition();
        Map<String, String> predicateParams1 = new HashMap<>(8);
        definition1.setId("baidu_route");
        predicate1.setName("Path");
        predicateParams1.put("pattern", "/baidu");
        predicate1.setArgs(predicateParams1);
        definition1.setPredicates(Arrays.asList(predicate1));
        URI uri1 = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri();
        definition1.setUri(uri1);
//        System.out.println("definition1："+JSON.toJSONString(definition1));

    }


}
